package biz

import (
	"context"
	"fmt"
	"geekbang-go-homework/week04/internal/domain"
	e "geekbang-go-homework/week04/internal/pkg/errors"

	"github.com/pkg/errors"
)

type orderUsecase struct {
	orderRepo domain.OrderRepo
}

// NewOrderUsecase .
func NewOrderUsecase(repo domain.OrderRepo) domain.OrderUsecase {
	return &orderUsecase{
		orderRepo: repo,
	}
}

// ListOrders get order list
func (o *orderUsecase) ListOrders(ctx context.Context, search *domain.SearchOrderList) (li []*domain.Order, p domain.Pagination, err error) {
	li, total, err := o.orderRepo.ListOrders(ctx, search)
	if err != nil {
		return
	}
	p.Total = total
	p.PageNo = search.PageNo
	p.PageSize = search.PageSize
	return
}

// GetOrder get order by id
func (o *orderUsecase) GetOrder(ctx context.Context, id int64) (*domain.Order, error) {
	return o.orderRepo.GetOrder(ctx, id)
}

// CreateOrder create order
func (o *orderUsecase) CreateOrder(ctx context.Context, do *domain.Order) (re *domain.Order, err error) {
	do.Price = 0
	for _, item := range do.Items {
		do.Price += item.Price
	}
	do.Status = domain.ONGOING
	err = WithTx(ctx, o.orderRepo, func(repo domain.OrderRepo) error {
		re, err = repo.CreateOrder(ctx, do)
		return err
	})
	return
}

// UpdateOrder update order
func (o *orderUsecase) UpdateOrder(ctx context.Context, do *domain.Order, mask []string) (re *domain.Order, err error) {
	err = WithTx(ctx, o.orderRepo, func(repo domain.OrderRepo) error {
		re, err = repo.UpdateOrder(ctx, do, mask)
		return err
	})
	return
}

// DeleteOrder delete order by id
func (o *orderUsecase) DeleteOrder(ctx context.Context, id int64) error {
	return WithTx(ctx, o.orderRepo, func(repo domain.OrderRepo) error {
		err := repo.DeleteOrder(ctx, id)
		if e.IsNotFound(err) {
			return nil
		}
		return err
	})
}

// WithTx exec transaction
func WithTx(ctx context.Context, repo domain.OrderRepo, fn func(domain.OrderRepo) error) (err error) {
	tx, err := repo.Tx(ctx)
	if err != nil {
		return err
	}
	defer func() {
		if v := recover(); v != nil {
			if rerr := tx.Rollback(); rerr != nil {
				err = errors.Wrapf(err, "rolling back transaction: %v", rerr)
			} else {
				err = errors.New(fmt.Sprint(v))
			}
		}
	}()
	if err = fn(tx); err != nil {
		if rerr := tx.Rollback(); rerr != nil {
			err = errors.Wrapf(err, "rolling back transaction: %v", rerr)
		}
		return err
	}
	select {
	case <-ctx.Done():
		if rerr := tx.Rollback(); rerr != nil {
			return errors.Wrapf(ctx.Err(), "rolling back transaction: %v", rerr)
		}
		return ctx.Err()
	default:
		return tx.Commit()
	}
}
